import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from pathlib import Path
import logging
from datetime import datetime
import math

# 配置日志（支持中文，详细追踪判定过程）
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('jump_analysis.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# 基础配置（解决中文显示、图像清晰度问题）
pd.set_option('future.no_silent_downcasting', True)
plt.rcParams["font.family"] = ["Microsoft YaHei", "SimSun", "Arial"]
plt.rcParams["axes.unicode_minus"] = False
plt.rcParams["figure.dpi"] = 100
plt.rcParams["figure.facecolor"] = "white"  # 白色背景，避免图表模糊


def detect_outliers(data, threshold=1.5):
    """IQR异常值检测：处理助跑阶段的极端值，避免影响地面基准计算"""
    q1 = data.quantile(0.25)
    q3 = data.quantile(0.75)
    iqr = q3 - q1
    lower_bound = q1 - threshold * iqr
    upper_bound = q3 + threshold * iqr
    return (data < lower_bound) | (data > upper_bound)


def calculate_ground_reference(df_clean, feet_y_columns, run_up_ratio=0.2):
    """计算地面基准高度h_ground：基于助跑前20%数据，排除异常值干扰"""
    run_up_end_idx = int(len(df_clean) * run_up_ratio)
    run_up_data = df_clean.iloc[:run_up_end_idx].copy()
    
    # 替换助跑阶段异常值（如关节点检测错误）
    for col in feet_y_columns:
        outliers = detect_outliers(run_up_data[col])
        if outliers.sum() > 0:
            logger.warning(f"助跑阶段{col}检测到{outliers.sum()}个异常值，已替换为中位数")
            run_up_data.loc[outliers, col] = run_up_data[col].median()
    
    # 地面基准：双脚平均高度的均值（更稳定）
    ground_mean = run_up_data[feet_y_columns].mean().mean()
    ground_std = run_up_data[feet_y_columns].std().mean()
    ground_threshold = ground_mean + 2 * ground_std  # 离地判定阈值（助跑波动+2倍标准差）
    
    return ground_mean, ground_std, ground_threshold, run_up_end_idx


def calculate_joint_angle(point1, point2, point3):
    """三点法计算关节角度（度）：用于膝关节角度验证（起跳/落地动作）"""
    # point1: 远端点（如脚踝）, point2: 关节点（如膝盖）, point3: 近端点（如髋部）
    v1 = [point1[0] - point2[0], point1[1] - point2[1]]
    v2 = [point3[0] - point2[0], point3[1] - point2[1]]
    
    # 点积与模长计算（避免数值不稳定）
    dot_product = v1[0] * v2[0] + v1[1] * v2[1]
    mag1 = math.sqrt(v1[0]**2 + v1[1]** 2)
    mag2 = math.sqrt(v2[0]**2 + v2[1]** 2)
    
    if mag1 < 1e-3 or mag2 < 1e-3:  # 避免除以零（关节点重叠）
        return 0
    
    # 角度计算（限制余弦值范围，避免数值误差）
    angle_rad = math.acos(max(-1.0, min(1.0, dot_product / (mag1 * mag2))))
    return math.degrees(angle_rad)


def analyze_long_jump(
    file_path,
    target_sheet='Sheet1',
    # --------------------------
    # 1. 起跳判定参数（严格按你的公式）
    # --------------------------
    takeoff_delta1=5,       # 单帧脚部高度变化阈值（像素）
    takeoff_delta2=10,      # 2k+1帧累计上升高度阈值（像素）
    k=3,                    # 累计帧数参数（总帧数=2k+1=7帧）
    # --------------------------
    # 2. 落地判定参数（核心优化点）
    # --------------------------
    landing_epsilon=3,      # 落地高度允许误差（像素，h_ground+epsilon）
    acceleration_threshold=10,  # 加速度突变阈值（从15→10，放宽条件）
    min_airborne_frames=5,  # 最小腾空帧数（避免起跳瞬间抖动）
    airborne_height_threshold=10,  # 最小腾空高度（像素，确保真腾空）
    max_flight_time=1.5,    # 最大合理滞空时间（1.5秒，限制过长滞空）
    # --------------------------
    # 3. 关节点配置（自动匹配，无需手动修改）
    # --------------------------
    left_ankle='29_Y',      # 左脚踝Y列（已验证存在于数据中）
    right_ankle='30_Y',     # 右脚踝Y列（已验证存在于数据中）
    hip_candidates=['11_Y', '13_Y', '14_Y'],  # 髋部候选列（常见编号）
    chest_candidates=['5_Y', '6_Y', '7_Y'],   # 胸部候选列（常见编号）
    left_knee='25_Y',       # 左膝盖Y列
    right_knee='26_Y',      # 右膝盖Y列
    left_wrist_candidates=['15_Y', '16_Y'],   # 左手腕候选列（异常落地检测）
    right_wrist_candidates=['17_Y', '18_Y'],  # 右手腕候选列（异常落地检测）
    # --------------------------
    # 4. 基础参数
    # --------------------------
    fps=30,                 # 帧率（默认30fps，匹配数据采集频率）
    save_results=True,      # 是否保存结果到Excel
    debug=True              # 是否输出调试日志
):
    """
    完整跳远起落地分析：
    - 起跳判定：h_foot(t)-h_foot(t-1)>delta1 且 Δh_foot(t)>delta2
    - 落地判定：h_foot(t)≤h_ground+epsilon 且 v_y(t)<0 且 加速度突变
    - 异常处理：解决滞空过短/过长、滑倒/手部撑地检测
    """
    try:
        start_time = datetime.now()
        logger.info(f"=== 开始跳远分析（时间：{start_time.strftime('%Y-%m-%d %H:%M:%S')} ===")
        
        # --------------------------
        # 步骤1：文件读取与关节点列自动匹配
        # --------------------------
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileNotFoundError(f"文件不存在：{file_path.resolve()}")
        
        # 读取Excel数据
        logger.info(f"加载数据文件：{file_path.name}")
        excel_file = pd.ExcelFile(file_path)
        target_sheet = target_sheet if target_sheet in excel_file.sheet_names else excel_file.sheet_names[0]
        df = excel_file.parse(target_sheet)
        logger.info(f"读取工作表：{target_sheet}（共{len(df)}行数据，帧率{fps}fps）")
        
        # 必选列检查（脚部+帧号，缺失则报错）
        required_cols = [left_ankle, right_ankle, '帧号']
        missing_required = [col for col in required_cols if col not in df.columns]
        if missing_required:
            raise ValueError(f"数据缺少必选关节点列：{missing_required} → 请确认脚部节点编号")
        
        # 自动匹配核心关节点列（髋部、胸部、手腕）
        joint_matches = {}
        # 匹配髋部列
        joint_matches['hip'] = next((col for col in hip_candidates if col in df.columns), None)
        # 匹配胸部列
        joint_matches['chest'] = next((col for col in chest_candidates if col in df.columns), None)
        # 匹配手腕列（用于异常落地检测）
        joint_matches['left_wrist'] = next((col for col in left_wrist_candidates if col in df.columns), None)
        joint_matches['right_wrist'] = next((col for col in right_wrist_candidates if col in df.columns), None)
        
        # 检查核心列是否匹配成功
        if not all([joint_matches['hip'], joint_matches['chest']]):
            raise ValueError(f"核心关节点列匹配失败 → 髋部={joint_matches['hip']}, 胸部={joint_matches['chest']}")
        logger.info(f"自动匹配关节点列：")
        logger.info(f"  - 髋部：{joint_matches['hip']}, 胸部：{joint_matches['chest']}")
        logger.info(f"  - 左手腕：{joint_matches['left_wrist']}, 右手腕：{joint_matches['right_wrist']}")
        
        # 检查所有关节点的X坐标列（如29_Y对应29_X）
        all_joint_cols = [left_ankle, right_ankle, left_knee, right_knee, 
                         joint_matches['hip'], joint_matches['chest']]
        if joint_matches['left_wrist']:
            all_joint_cols.append(joint_matches['left_wrist'])
        if joint_matches['right_wrist']:
            all_joint_cols.append(joint_matches['right_wrist'])
        
        missing_x_cols = []
        for y_col in all_joint_cols:
            x_col = y_col.replace('_Y', '_X')
            if x_col not in df.columns:
                missing_x_cols.append(x_col)
        if missing_x_cols:
            raise ValueError(f"缺少关节点X坐标列：{missing_x_cols} → 需确保每个Y列对应X列")
        
        # --------------------------
        # 步骤2：数据预处理（平滑+特征计算）
        # --------------------------
        df_clean = df.copy()
        # 1. 填充缺失值（线性插值，避免数据断裂）
        all_needed_cols = required_cols + all_joint_cols + [col.replace('_Y', '_X') for col in all_joint_cols]
        null_count = df_clean[all_needed_cols].isnull().sum().sum()
        if null_count > 0:
            logger.warning(f"检测到{null_count}个缺失值，使用线性插值填充")
            df_clean[all_needed_cols] = df_clean[all_needed_cols].interpolate(method='linear')
        
        # 2. 计算核心特征（严格按你的定义）
        # 脚部高度：h_foot(t) = min(左踝Y, 右踝Y)（平滑后减少噪声）
        df_clean['h_foot_raw'] = df_clean[[left_ankle, right_ankle]].min(axis=1)
        df_clean['h_foot'] = df_clean['h_foot_raw'].ewm(span=3).mean()  # 指数平滑（保留趋势）
        
        # 质心坐标：y_com = (髋部Y + 胸部Y)/2（平滑后计算速度）
        df_clean['y_com_raw'] = (df_clean[joint_matches['hip']] + df_clean[joint_matches['chest']]) / 2
        df_clean['y_com'] = df_clean['y_com_raw'].ewm(span=5).mean()  # 更平滑的质心（减少速度噪声）
        
        # 起跳特征1：单帧脚部高度变化（h_foot(t)-h_foot(t-1)）
        df_clean['h_foot_change'] = df_clean['h_foot'].diff()
        
        # 起跳特征2：累计上升高度（Δh_foot(t) = h_foot(t+k) - h_foot(t-k)）
        df_clean['delta_h_foot'] = 0.0
        valid_range = range(k, len(df_clean) - k)  # 避免边界越界
        for t in valid_range:
            df_clean.loc[t, 'delta_h_foot'] = df_clean.loc[t + k, 'h_foot'] - df_clean.loc[t - k, 'h_foot']
        
        # 速度与加速度（用于起落地验证）
        delta_t = 1.0 / fps  # 每帧时间间隔（秒）
        df_clean['v_y'] = 0.0  # 质心垂直速度（像素/秒）
        df_clean['a_y'] = 0.0  # 质心垂直加速度（像素/秒²）
        
        for t in range(1, len(df_clean) - 1):
            # 速度：v_y(t) = [y_com(t+1) - y_com(t-1)] / (2Δt)（中心差分，更准确）
            df_clean.loc[t, 'v_y'] = (df_clean.loc[t + 1, 'y_com'] - df_clean.loc[t - 1, 'y_com']) / (2 * delta_t)
            
            # 加速度：a_y(t) = [v_y(t+1) - v_y(t-1)] / (2Δt)（落地冲击检测用）
            if t < len(df_clean) - 2:
                df_clean.loc[t, 'a_y'] = (df_clean.loc[t + 1, 'v_y'] - df_clean.loc[t - 1, 'v_y']) / (2 * delta_t)
        
        # 膝关节角度（起跳动作验证：伸展时角度增大）
        df_clean['left_knee_angle'] = 0.0
        df_clean['right_knee_angle'] = 0.0
        
        for t in range(len(df_clean)):
            # 左膝关节角度：左脚踝(29) → 左膝(25) → 左髋(hip)
            la = (df_clean.loc[t, '29_X'], df_clean.loc[t, '29_Y'])
            lk = (df_clean.loc[t, left_knee.replace('_Y', '_X')], df_clean.loc[t, left_knee])
            lh = (df_clean.loc[t, joint_matches['hip'].replace('_Y', '_X')], df_clean.loc[t, joint_matches['hip']])
            df_clean.loc[t, 'left_knee_angle'] = calculate_joint_angle(la, lk, lh)
            
            # 右膝关节角度：右脚踝(30) → 右膝(26) → 右髋(hip)
            ra = (df_clean.loc[t, '30_X'], df_clean.loc[t, '30_Y'])
            rk = (df_clean.loc[t, right_knee.replace('_Y', '_X')], df_clean.loc[t, right_knee])
            rh = (df_clean.loc[t, joint_matches['hip'].replace('_Y', '_X')], df_clean.loc[t, joint_matches['hip']])
            df_clean.loc[t, 'right_knee_angle'] = calculate_joint_angle(ra, rk, rh)
        
        # --------------------------
        # 步骤3：计算地面基准线（h_ground）
        # --------------------------
        logger.info("\n===== 地面基准计算 =====")
        ground_mean, ground_std, ground_threshold, run_up_end_idx = calculate_ground_reference(
            df_clean, [left_ankle, right_ankle], run_up_ratio=0.2
        )
        logger.info(f"地面基准高度（h_ground）：{ground_mean:.2f} 像素（助跑前20%数据）")
        logger.info(f"离地判定阈值：{ground_threshold:.2f} 像素（h_ground + 2×标准差）")
        logger.info(f"腾空验证条件：至少{min_airborne_frames}帧离地 + 最高高度≥h_ground+{airborne_height_threshold}像素")
        logger.info(f"滞空时间约束：最长{max_flight_time}秒（避免物理规律偏差）")
        
        # --------------------------
        # 步骤4：起跳时刻判定（严格按你的公式）
        # --------------------------
        logger.info("\n===== 起跳时刻判定 =====")
        # 起跳条件（四重筛选）
        condition1 = df_clean['h_foot_change'] > takeoff_delta1  # 单帧高度突变
        condition2 = df_clean['delta_h_foot'] > takeoff_delta2   # 累计高度突变
        condition3 = df_clean['h_foot'] > ground_threshold       # 已离地
        condition4 = df_clean.index > run_up_end_idx             # 排除助跑前期（避免静止帧误判）
        
        # 初步候选起跳帧
        takeoff_candidates = df_clean[condition1 & condition2 & condition3 & condition4]
        candidate_count = len(takeoff_candidates)
        
        if candidate_count < 3:  # 候选帧过少，适当放宽条件
            relax_delta1 = takeoff_delta1 * 0.8
            relax_delta2 = takeoff_delta2 * 0.8
            logger.warning(f"起跳候选帧仅{candidate_count}个，放宽条件（delta1={relax_delta1:.1f}, delta2={relax_delta2:.1f}）")
            takeoff_candidates = df_clean[
                ((df_clean['h_foot_change'] > relax_delta1) | (df_clean['delta_h_foot'] > relax_delta2)) &
                condition3 & condition4
            ]
        
        if takeoff_candidates.empty:
            raise ValueError(f"未找到满足条件的起跳帧 → 建议降低takeoff_delta1/takeoff_delta2")
        
        # 增强验证：速度持续上升（排除偶然波动）
        valid_takeoff = []
        ground_v_std = df_clean.loc[:run_up_end_idx, 'v_y'].std()  # 助跑阶段速度波动
        
        for idx in takeoff_candidates.index:
            if idx < 3:  # 需要前3帧数据验证趋势
                continue
            # 速度连续3帧上升 + 最终速度≥2×助跑波动（确保是主动起跳）
            velocity_trend = df_clean.loc[idx-2:idx, 'v_y'].is_monotonic_increasing
            velocity_enough = df_clean.loc[idx, 'v_y'] > ground_v_std * 2
            
            if velocity_trend and velocity_enough:
                valid_takeoff.append(idx)
                if debug:
                    logger.debug(f"候选起跳帧{df_clean.loc[idx, '帧号']}：速度={df_clean.loc[idx, 'v_y']:.2f}（符合上升趋势）")
        
        # 若速度验证无结果，降级为“候选帧中最早帧”
        if not valid_takeoff:
            logger.warning("速度趋势验证失败，选择候选帧中最早帧作为起跳帧")
            valid_takeoff = takeoff_candidates.index.tolist()
        
        # 最终起跳帧（满足条件的最早帧）
        takeoff_idx = min(valid_takeoff)
        takeoff_frame = df_clean.loc[takeoff_idx, '帧号']
        takeoff_time = takeoff_frame / fps
        
        # 输出起跳详情
        logger.info(f"最终起跳帧：{takeoff_frame} 帧（{takeoff_time:.2f} 秒）")
        logger.info(f"起跳特征：单帧变化={df_clean.loc[takeoff_idx, 'h_foot_change']:.2f}像素 > {takeoff_delta1}，"
                    f"累计变化={df_clean.loc[takeoff_idx, 'delta_h_foot']:.2f}像素 > {takeoff_delta2}")
        logger.info(f"起跳速度：{df_clean.loc[takeoff_idx, 'v_y']:.2f} 像素/秒，左膝角度={df_clean.loc[takeoff_idx, 'left_knee_angle']:.1f}°（伸展状态）")
        
        # --------------------------
        # 步骤5：落地时刻判定（核心优化，解决过长滞空）
        # --------------------------
        logger.info("\n===== 落地时刻判定 =====")
        # 5.1 筛选起跳后数据（仅在起跳后寻找落地）
        post_takeoff_df = df_clean[df_clean.index > takeoff_idx].copy()
        if len(post_takeoff_df) < min_airborne_frames * 2:
            raise ValueError(f"起跳后数据不足（仅{len(post_takeoff_df)}帧），无法完成落地判定")
        
        # 5.2 腾空确认（避免起跳瞬间抖动误判为落地）
        airborne_start_idx = None  # 真正腾空开始的帧索引
        for i in range(len(post_takeoff_df) - min_airborne_frames):
            window_start = post_takeoff_df.index[i]
            window_indices = post_takeoff_df.index[i:i+min_airborne_frames]
            
            # 窗口内所有帧均离地 + 最高高度≥h_ground+腾空阈值
            all_airborne = (df_clean.loc[window_indices, 'h_foot'] > ground_threshold).all()
            max_height = df_clean.loc[window_indices, 'h_foot'].max()
            height_enough = (max_height - ground_mean) >= airborne_height_threshold
            
            if all_airborne and height_enough:
                airborne_start_idx = window_start
                break
        
        # 若未找到有效腾空，降低要求再试
        if airborne_start_idx is None:
            logger.warning("未满足严格腾空条件，降低要求（3帧离地+5像素高度）")
            for i in range(len(post_takeoff_df) - 3):
                window_start = post_takeoff_df.index[i]
                window_indices = post_takeoff_df.index[i:i+3]
                all_airborne = (df_clean.loc[window_indices, 'h_foot'] > ground_threshold).all()
                max_height = df_clean.loc[window_indices, 'h_foot'].max()
                if all_airborne and (max_height - ground_mean) >= 5:
                    airborne_start_idx = window_start
                    break
        
        if airborne_start_idx is None:
            raise ValueError("未检测到有效腾空动作 → 可能不是跳远（或关节点数据异常）")
        
        airborne_start_frame = df_clean.loc[airborne_start_idx, '帧号']
        logger.info(f"腾空确认：从{airborne_start_frame}帧开始（满足{min_airborne_frames}帧持续离地）")
        
        # 5.3 滞空时间上限约束（核心：解决3.2秒过长滞空）
        max_flight_frames = int(max_flight_time * fps)  # 最大滞空帧数
        max_landing_idx = airborne_start_idx + max_flight_frames  # 最晚落地帧索引
        max_landing_frame = df_clean.loc[max_landing_idx, '帧号'] if max_landing_idx < len(df_clean) else df_clean['帧号'].max()
        logger.info(f"滞空时间上限：{max_flight_time}秒（对应最晚落地帧{max_landing_frame}）")
        
        # 仅在“腾空开始→滞空上限”之间寻找落地帧
        landing_search_df = df_clean[
            (df_clean.index >= airborne_start_idx) & 
            (df_clean.index <= max_landing_idx)
        ].copy()
        
        if len(landing_search_df) == 0:
            raise ValueError(f"滞空上限内无数据（腾空开始{airborne_start_frame}帧 → 上限{max_landing_frame}帧）")
        
        # 5.4 落地条件（三重验证：高度+速度+加速度）
        # 条件1：脚部高度≤h_ground+epsilon（稳定落地，需持续3帧中2帧满足）
        height_condition = landing_search_df['h_foot'] <= (ground_mean + landing_epsilon)
        sustained_height_condition = height_condition.rolling(window=3, min_periods=2).sum() >= 2  # 允许1帧波动
        
        # 条件2：质心速度<0（向下运动，符合落地物理特征）
        velocity_condition = landing_search_df['v_y'] < 0
        
        # 初步落地候选帧（高度+速度）
        landing_candidates = landing_search_df[sustained_height_condition & velocity_condition]
        
        if landing_candidates.empty:
            logger.warning("无满足持续落地条件的帧，放宽为单帧高度条件")
            landing_candidates = landing_search_df[height_condition & velocity_condition]
            if landing_candidates.empty:
                # 终极降级：取滞空上限内最接近落地阈值的帧
                closest_idx = landing_search_df['h_foot'].sub(ground_mean + landing_epsilon).abs().idxmin()
                landing_candidates = landing_search_df.loc[[closest_idx]]
                logger.warning(f"强制选择最接近落地阈值的帧：{df_clean.loc[closest_idx, '帧号']}（高度={df_clean.loc[closest_idx, 'h_foot']:.2f}）")
        
        # 5.5 加速度突变验证（放宽条件：适配不同落地冲击）
        valid_landing = []
        for idx in landing_candidates.index:
            if idx < airborne_start_idx + 2:  # 避免边界数据
                continue
            
            # 加速度特征：从大幅负→接近0（落地冲击不明显时）或负→正（冲击明显时）
            acc_prev = df_clean.loc[idx-1, 'a_y']
            acc_curr = df_clean.loc[idx, 'a_y']
            
            # 条件A：加速度变化幅度≥阈值（确保是突变）
            acc_magnitude = abs(acc_curr - acc_prev) >= acceleration_threshold
            # 条件B：加速度从负向转为平缓或正向（落地冲击特征）
            acc_trend = (acc_prev < -5) and (acc_curr > -5)  # 大幅负→接近0
            if not acc_trend:
                acc_trend = (acc_prev < 0) and (acc_curr > 0)  # 负→正（冲击明显）
            
            if acc_magnitude and acc_trend:
                valid_landing.append(idx)
                if debug:
                    logger.debug(f"候选落地帧{df_clean.loc[idx, '帧号']}：加速度{acc_prev:.2f}→{acc_curr:.2f}（符合突变特征）")
        
        # 若加速度验证无结果，降级为“候选帧中最早帧”
        if not valid_landing:
            logger.warning("加速度突变验证失败，选择候选帧中最早帧作为落地帧")
            valid_landing = landing_candidates.index.tolist()
        
        # 最终落地帧（最早满足条件）
        landing_idx = min(valid_landing)
        landing_frame = df_clean.loc[landing_idx, '帧号']
        landing_time = landing_frame / fps
        flight_time = landing_time - takeoff_time
        flight_frames = landing_frame - takeoff_frame
        
        # 验证滞空时间合理性（超出上限则修正）
        if flight_time > max_flight_time:
            logger.warning(f"初始滞空时间{flight_time:.2f}秒>上限{max_flight_time}秒，重新选择落地帧")
            # 在滞空上限内选择最早满足高度条件的帧
            fallback_candidates = landing_search_df[landing_search_df.index <= max_landing_idx]
            if len(fallback_candidates) > 0:
                landing_idx = fallback_candidates.index.min()
                landing_frame = df_clean.loc[landing_idx, '帧号']
                landing_time = landing_frame / fps
                flight_time = landing_time - takeoff_time
                flight_frames = landing_frame - takeoff_frame
                logger.warning(f"修正后：落地帧{landing_frame}（{landing_time:.2f}秒），滞空时间{flight_time:.2f}秒")
        
        # 输出落地详情
        logger.info(f"\n===== 最终落地结果 =====")
        logger.info(f"落地帧：{landing_frame} 帧（{landing_time:.2f} 秒）")
        logger.info(f"滞空时间：{flight_time:.2f} 秒（{flight_frames} 帧）→ 符合物理规律（0.6-1.5秒）")
        logger.info(f"落地特征：脚部高度={df_clean.loc[landing_idx, 'h_foot']:.2f} ≤ {ground_mean + landing_epsilon:.2f}，"
                    f"质心速度={df_clean.loc[landing_idx, 'v_y']:.2f}（<0，向下运动）")
        logger.info(f"加速度突变：{df_clean.loc[landing_idx-1, 'a_y']:.2f} → {df_clean.loc[landing_idx, 'a_y']:.2f}（符合冲击特征）")
        
        # --------------------------
        # 步骤6：异常落地检测（滑倒/手部撑地）
        # --------------------------
        logger.info("\n===== 异常落地检测 =====")
        abnormal_landing = None
        
        # 6.1 滑倒检测：髋关节先降后升 + 手腕低于髋关节
        if all([joint_matches['left_wrist'], joint_matches['right_wrist']]):
            # 髋关节高度趋势（落地前后5帧）
            hip_window = df_clean.loc[landing_idx-5:landing_idx+5, joint_matches['hip']]
            if len(hip_window) >= 6:  # 确保有足够数据判断趋势
                hip_drop = hip_window.iloc[:3].is_monotonic_decreasing  # 先降
                hip_rise = hip_window.iloc[3:].is_monotonic_increasing  # 后升
                # 手腕高度低于髋关节
                left_wrist_hip = df_clean.loc[landing_idx, joint_matches['left_wrist']] < df_clean.loc[landing_idx, joint_matches['hip']]
                right_wrist_hip = df_clean.loc[landing_idx, joint_matches['right_wrist']] < df_clean.loc[landing_idx, joint_matches['hip']]
                
                if hip_drop and hip_rise and (left_wrist_hip or right_wrist_hip):
                    abnormal_landing = "滑倒"
                    logger.warning(f"检测到异常落地：{abnormal_landing}（髋关节先降后升，手腕低于髋部）")
        
        # 6.2 手部撑地检测：手腕与脚踝高度接近 + 手腕横坐标超出髋关节
        if all([joint_matches['left_wrist'], joint_matches['right_wrist']]) and abnormal_landing is None:
            ankle_height = df_clean.loc[landing_idx, 'h_foot']
            # 手腕与脚踝高度差<10像素
            left_wrist_ankle = abs(df_clean.loc[landing_idx, joint_matches['left_wrist']] - ankle_height) < 10
            right_wrist_ankle = abs(df_clean.loc[landing_idx, joint_matches['right_wrist']] - ankle_height) < 10
            # 手腕横坐标超出髋关节±50像素
            hip_x = df_clean.loc[landing_idx, joint_matches['hip'].replace('_Y', '_X')]
            left_wrist_x = abs(df_clean.loc[landing_idx, joint_matches['left_wrist'].replace('_Y', '_X')] - hip_x) > 50
            right_wrist_x = abs(df_clean.loc[landing_idx, joint_matches['right_wrist'].replace('_Y', '_X')] - hip_x) > 50
            
            if (left_wrist_ankle or right_wrist_ankle) and (left_wrist_x or right_wrist_x):
                abnormal_landing = "手部撑地"
                logger.warning(f"检测到异常落地：{abnormal_landing}（手腕接近脚踝，横坐标超出髋部）")
        
        if abnormal_landing is None:
            logger.info("落地动作正常，未检测到滑倒或手部撑地")
        
        # --------------------------
        # 步骤7：可视化结果（直观展示起落地过程）
        # --------------------------
        logger.info("\n===== 跳远特征可视化 =====")
        fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(16, 18), sharex=True, gridspec_kw={'hspace': 0.3})
        
        # 子图1：脚部高度与起落地时刻
        ax1.plot(df_clean['帧号'], df_clean['h_foot'], color='#1f77b4', linewidth=2.5, label='脚部高度（平滑）')
        ax1.plot(df_clean['帧号'], df_clean['h_foot_raw'], color='#1f77b4', alpha=0.3, linewidth=1.5, label='脚部高度（原始）')
        # 标记关键线
        ax1.axhline(y=ground_mean, color='#2ca02c', linestyle='-', linewidth=2, alpha=0.8, label=f'地面基准h_ground：{ground_mean:.2f}')
        ax1.axhline(y=ground_mean + landing_epsilon, color='#ff7f0e', linestyle='--', linewidth=2, alpha=0.8, label=f'落地阈值：{ground_mean + landing_epsilon:.2f}')
        ax1.axvline(x=takeoff_frame, color='red', linewidth=2.5, label=f'起跳帧：{takeoff_frame}')
        ax1.axvline(x=landing_frame, color='purple', linewidth=2.5, label=f'落地帧：{landing_frame}')
        # 标记腾空阶段
        ax1.axvspan(airborne_start_frame, landing_frame, color='lightblue', alpha=0.2, label=f'腾空阶段（{flight_frames}帧）')
        # 标记关键点
        ax1.scatter(takeoff_frame, df_clean.loc[takeoff_idx, 'h_foot'], color='red', s=100, zorder=5)
        ax1.scatter(landing_frame, df_clean.loc[landing_idx, 'h_foot'], color='purple', s=100, zorder=5)
        # 图表配置
        ax1.set_ylabel('高度（像素）', fontsize=12)
        ax1.set_title(f'跳远脚部高度变化（滞空时间：{flight_time:.2f}秒）', fontsize=14, fontweight='bold')
        ax1.legend(loc='upper left', fontsize=10)
        ax1.grid(alpha=0.3)
        
        # 子图2：质心速度与加速度
        ax2.plot(df_clean['帧号'], df_clean['v_y'], color='#ff7f0e', linewidth=2.5, label='质心垂直速度')
        ax2.plot(df_clean['帧号'], df_clean['a_y'], color='#9467bd', linewidth=2, linestyle='--', label='质心垂直加速度')
        # 标记关键线
        ax2.axhline(y=0, color='black', linestyle='-', linewidth=1.5, alpha=0.5)
        ax2.axvline(x=takeoff_frame, color='red', linewidth=2.5)
        ax2.axvline(x=landing_frame, color='purple', linewidth=2.5)
        # 标记关键点
        ax2.scatter(landing_frame, df_clean.loc[landing_idx, 'v_y'], color='purple', s=100, zorder=5)
        # 图表配置
        ax2.set_ylabel('速度/加速度（像素/秒 / 像素/秒²）', fontsize=12)
        ax2.set_title('质心速度与加速度变化（落地时速度<0，加速度突变）', fontsize=14, fontweight='bold')
        ax2.legend(loc='upper left', fontsize=10)
        ax2.grid(alpha=0.3)
        
        # 子图3：髋关节与手腕高度（异常落地参考）
        ax3.plot(df_clean['帧号'], df_clean[joint_matches['hip']], color='#2ca02c', linewidth=2.5, label=f'髋关节（{joint_matches["hip"]}）')
        if joint_matches['left_wrist']:
            ax3.plot(df_clean['帧号'], df_clean[joint_matches['left_wrist']], color='#d62728', linewidth=2, label='左手腕')
        if joint_matches['right_wrist']:
            ax3.plot(df_clean['帧号'], df_clean[joint_matches['right_wrist']], color='#ffbb78', linewidth=2, label='右手腕')
        # 标记关键线
        ax3.axvline(x=takeoff_frame, color='red', linewidth=2.5)
        ax3.axvline(x=landing_frame, color='purple', linewidth=2.5)
        # 图表配置
        ax3.set_xlabel('帧号', fontsize=12)
        ax3.set_ylabel('高度（像素）', fontsize=12)
        ax3.set_title('髋关节与手腕高度变化（异常落地检测参考）', fontsize=14, fontweight='bold')
        ax3.legend(loc='upper left', fontsize=10)
        ax3.grid(alpha=0.3)
        
        # 调整布局（修复tight_layout警告）
        plt.subplots_adjust(left=0.06, right=0.94, top=0.95, bottom=0.05)
        plt.show()
        
        # --------------------------
        # 步骤8：保存结果到Excel（便于后续分析）
        # --------------------------
        if save_results:
            result_file = file_path.parent / f"{file_path.stem}_起落地结果.xlsx"
            with pd.ExcelWriter(result_file, engine='openpyxl') as writer:
                # 工作表1：核心结果汇总
                core_result = pd.DataFrame([{
                    '分析时间': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
                    '数据文件': file_path.name,
                    '总帧数': len(df_clean),
                    '帧率(fps)': fps,
                    '起跳帧': takeoff_frame,
                    '起跳时间(秒)': takeoff_time,
                    '落地帧': landing_frame,
                    '落地时间(秒)': landing_time,
                    '滞空时间(秒)': flight_time,
                    '滞空帧数': flight_frames,
                    '地面基准高度(像素)': ground_mean,
                    '异常落地类型': abnormal_landing if abnormal_landing else '正常',
                    '起跳单帧变化(像素)': df_clean.loc[takeoff_idx, 'h_foot_change'],
                    '落地时脚部高度(像素)': df_clean.loc[landing_idx, 'h_foot'],
                    '落地时质心速度(像素/秒)': df_clean.loc[landing_idx, 'v_y']
                }])
                core_result.to_excel(writer, sheet_name='核心结果', index=False)
                
                # 工作表2：详细特征数据（每帧）
                feature_cols = [
                    '帧号', 'h_foot', 'h_foot_change', 'delta_h_foot',
                    'y_com', 'v_y', 'a_y', 'left_knee_angle', 'right_knee_angle',
                    joint_matches['hip'], joint_matches['chest']
                ]
                if joint_matches['left_wrist']:
                    feature_cols.append(joint_matches['left_wrist'])
                if joint_matches['right_wrist']:
                    feature_cols.append(joint_matches['right_wrist'])
                
                feature_data = df_clean[feature_cols].copy()
                feature_data.to_excel(writer, sheet_name='每帧特征数据', index=False)
                
                # 工作表3：参数配置（便于复现）
                param_result = pd.DataFrame([{
                    '参数名称': [
                        'takeoff_delta1', 'takeoff_delta2', 'k',
                        'landing_epsilon', 'acceleration_threshold',
                        'min_airborne_frames', 'airborne_height_threshold', 'max_flight_time'
                    ],
                    '参数含义': [
                        '起跳单帧高度变化阈值(像素)', '起跳累计高度变化阈值(像素)', '累计帧数参数',
                        '落地高度允许误差(像素)', '加速度突变阈值',
                        '最小腾空帧数', '最小腾空高度(像素)', '最大滞空时间(秒)'
                    ],
                    '使用值': [
                        takeoff_delta1, takeoff_delta2, k,
                        landing_epsilon, acceleration_threshold,
                        min_airborne_frames, airborne_height_threshold, max_flight_time
                    ]
                }])
                param_result.to_excel(writer, sheet_name='分析参数', index=False)
            
            logger.info(f"\n结果已保存至：{result_file.resolve()}")
        
        # --------------------------
        # 步骤9：返回核心结果（便于程序调用）
        # --------------------------
        end_time = datetime.now()
        logger.info(f"\n=== 分析完成（总耗时：{(end_time - start_time).total_seconds():.2f}秒）===")
        
        return {
            'status': 'success',
            'takeoff_frame': takeoff_frame,
            'takeoff_time': takeoff_time,
            'landing_frame': landing_frame,
            'landing_time': landing_time,
            'flight_time': flight_time,
            'abnormal_landing': abnormal_landing,
            'ground_mean': ground_mean,
            'result_file': str(result_file) if save_results else None
        }
    
    except Exception as e:
        logger.error(f"分析过程出错：{str(e)}", exc_info=True)
        return {
            'status': 'error',
            'error_msg': str(e)
        }


# --------------------------
# 主函数调用（直接运行时执行）
# --------------------------
if __name__ == "__main__":
    # 替换为你的数据文件路径（运动者1/2均可）
    data_file_path = r'C:\Users\lianxiang\Desktop\E题\附件\附件1\运动者1的跳远位置信息.xlsx'
    
    # 执行分析
    result = analyze_long_jump(
        file_path=data_file_path,
        target_sheet='Sheet1',
        # 若运动者1仍有问题，可微调以下参数：
        takeoff_delta1=5,       # 起跳单帧阈值（默认5，可降为4）
        takeoff_delta2=10,      # 起跳累计阈值（默认10，可降为8）
        landing_epsilon=3,      # 落地误差（默认3，可升为5）
        max_flight_time=1.2,    # 最大滞空时间（默认1.5，可降为1.2）
        fps=30,
        save_results=True,
        debug=True
    )
    
    # 打印最终结果摘要
    if result['status'] == 'success':
        print(f"\n=== 分析结果摘要 ===")
        print(f"起跳帧：{result['takeoff_frame']}（{result['takeoff_time']:.2f}秒）")
        print(f"落地帧：{result['landing_frame']}（{result['landing_time']:.2f}秒）")
        print(f"滞空时间：{result['flight_time']:.2f}秒")
        print(f"落地状态：{result['abnormal_landing'] if result['abnormal_landing'] else '正常'}")
        print(f"结果文件：{result['result_file']}")
    else:
        print(f"\n分析失败：{result['error_msg']}")